﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using SimpleOrmCFPC.Apis.Db;
using SimpleOrmCFPC.Core.Model;
using SimpleOrmCFPC.Core.RequestsComputer;
using SimpleOrmCFPC.Core.RequestsComputer.Dialects;
using SimpleOrmCFPC.Core.RequestsComputer.Dialects.Sqlite;

namespace SimpleOrmCFPC.upgrade
{
    public class Upgrader
    {
        private IDb db;
        private ISchemaProvider schemaProvider;
        private CreateTableRequestComputer tableFactory;

        public Upgrader(IDb db, ISchemaProvider schemaProvider, CreateTableRequestComputer tableFactory)
        {
            this.db = db;
            this.schemaProvider = schemaProvider;
            this.tableFactory = tableFactory;
        }

        private void SqlRequest(String request)
        {
            db.ExecuteNonQuery(request);
        }

        public void Table<T>()
        {
            var tableName = typeof (T).GetEntityName();
            if (!schemaProvider.TableExists(tableName))
            {
                var createTableRequest = tableFactory.ComputeRequestForCreateTable<T>();
                SqlRequest(createTableRequest);
            }
            else
            {
                var newTableSchema = CreateTableSchema<T>();
                var currentTableMapping = schemaProvider.GetTableSchema(tableName);

                var columnNamesToCreate = newTableSchema.Columns.Select(c => c.Name).Except(currentTableMapping.Columns.Select(c => c.Name));
                var columnNamesToDelete = currentTableMapping.Columns.Select(c => c.Name).Except(newTableSchema.Columns.Select(c => c.Name));
                DeleteColumns(columnNamesToDelete, currentTableMapping);
                CreateColumns(columnNamesToCreate, newTableSchema);

                currentTableMapping = schemaProvider.GetTableSchema(tableName);
                var columnsToModify = newTableSchema.Columns.Except(currentTableMapping.Columns, new TableMappingComparer()).ToList();
                    DeleteColumns(from c in columnsToModify select c.Name, currentTableMapping);
                    CreateColumns(from c in columnsToModify select c.Name, newTableSchema);
            }
        }

        private void UpdateColumns(IEnumerable<ColumnMapping> columnsToModify)
        {
            
        }

        private void CreateColumns(IEnumerable<string> columnNamesToCreate, TableMapping newTableSchema)
        {
            DbToSqlTypeConverter sqlTypeConverter = new DbToSqlTypeConverter();
            foreach (var columnName in columnNamesToCreate)
            {
                var columnToAdd = (from c in newTableSchema.Columns where c.Name == columnName select c).First();
                var alterTableRequest = string.Format("ALTER TABLE {0} ADD {1} {2}", newTableSchema.Name, columnToAdd.Name, sqlTypeConverter.ConvertType(columnToAdd.Type, columnToAdd.MaxLength));
                SqlRequest(alterTableRequest);
            }
        }

        private void DeleteColumns(IEnumerable<string> columnNamesToDelete, TableMapping currentMapping)
        {
            var updatedTableColumns = (from c in currentMapping.Columns select c).ToList();
            updatedTableColumns.RemoveAll(s => columnNamesToDelete.Contains(s.Name));

            String columnsSeperated = string.Join(",", (from c in updatedTableColumns select c.Name).ToArray());

            db.ExecuteNonQuery("ALTER TABLE " + currentMapping.Name + " RENAME TO " + currentMapping.Name + "_old;");
            db.ExecuteNonQuery(tableFactory.ComputeRequestForCreateTable(new TableMapping() { Name = currentMapping.Name, Columns = updatedTableColumns }));
            db.ExecuteNonQuery("INSERT INTO " + currentMapping.Name + "(" + columnsSeperated + ") SELECT "
                    + columnsSeperated + " FROM " + currentMapping.Name + "_old;");
            db.ExecuteNonQuery("DROP TABLE " + currentMapping.Name + "_old;");
        }

        private TableMapping CreateTableSchema<T>()
        {
            TableMappingFactory mappingFactory = new TableMappingFactory(new ColumnMappingFactory());
            return mappingFactory.Create<T>();
        }
    }

    public class TableMappingComparer : IEqualityComparer<ColumnMapping>
    {
        public bool Equals(ColumnMapping x, ColumnMapping y)
        {
            return x.Name == y.Name && x.Type == y.Type;
        }

        public int GetHashCode(ColumnMapping obj)
        {
            return (obj.Name.ToLower() + obj.Type.ToString()).GetHashCode();
        }
    }

    public interface IColumnMappingFactory
    {
        ColumnMapping Create(PropertyInfo property);
    }


    class ColumnMappingFactory : IColumnMappingFactory
    {
        public ColumnMapping Create(PropertyInfo property)
        {
            return new ColumnMapping(property.GetColumnName(), property.IsPrimaryKey(),
                property.IsNotNull(),
                false, property.PropertyType.ToDbType(),
                property.GetMaxLength());
        }
    }

    public class TableMappingFactory
    {
        private IColumnMappingFactory columnMappingFactory;

        public TableMappingFactory(IColumnMappingFactory columnMappingFactory)
        {
            this.columnMappingFactory = columnMappingFactory;
        }


        public TableMapping Create<T>()
        {
            var tableMapping = new TableMapping {Name = typeof (T).GetEntityName()};
            foreach (var property in typeof (T).GetProperties())
                tableMapping.Columns.Add(columnMappingFactory.Create(property));
            return tableMapping;
        }
    }

    public class TableMapping
    {
        public TableMapping()
        {
            Columns = new List<ColumnMapping>();
        }

        public String Name { get; set; }

        public List<ColumnMapping> Columns { get; set; }
    }

    public class ColumnMapping
    {
        public ColumnMapping(string name, bool primaryKey, bool notNull, bool autoIncrement, DbType type, int maxLength)
        {
            Name = name;
            PrimaryKey = primaryKey;
            NotNull = notNull;
            AutoIncrement = autoIncrement;
            Type = type;
            MaxLength = maxLength;
        }

        public string Name { get; set; }

        public bool PrimaryKey { get; set; }

        public bool NotNull { get; set; }

        public bool AutoIncrement { get; set; }

        public DbType Type { get; set; }

        public int MaxLength { get; set; }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != typeof (ColumnMapping)) return false;
            return Equals((ColumnMapping) obj);
        }

        public bool Equals(ColumnMapping other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(other.Name, Name) && other.PrimaryKey.Equals(PrimaryKey) && other.NotNull.Equals(NotNull) && other.AutoIncrement.Equals(AutoIncrement) && Equals(other.Type, Type) && other.MaxLength == MaxLength;
        }
    }
}